
/*jslint browser: true, devel: true, bitwise: true, white: true, plusplus: true, forin:true, nomen:true */
/*global $:true, define:true */

/**
 * @module Test
 */

define(function() {
"use strict";

function ResultsNode(startResult,type,parent) {
	this.results = {
		type:type,
		start:startResult,
		end:{
			failed:false
		},
		time:0,
		children:[]
	};
	
	this.parent = parent;
	this.startTime = 0;
	this.children = [];
}

ResultsNode.prototype = {
	constructor: ResultsNode,
	
	addChild: function(startResult,type) {
		var child = new ResultsNode(startResult,type,this);
		this.children[this.children.length] = child;
		return child;
	},
	
	getResults: function() {
		var i,len = this.children.length,children = this.results.children;
		for (i = 0; i < len; ++i) {
			children[i] = this.children[i].getResults();
		}
		
		return this.results;
	}
};

/** 
 * 
 * @class Reporter
 * @constructor
 */
function Reporter(onReportDone) {
	this.CurrentParent = this.TopResults = undefined;
	this.Failures = [];
	
	this.StartTime = 0;	
	this.FullName = [];
	this.NameLevel = 0;
	
	if (typeof onReportDone !== 'function') {
		throw new Error('[Reporter.constructor] onReportDone callback not set');
	}
	this.OnReportDone = onReportDone;
	this.Report = {
		totalSuites:0,
		totalCases:0,
		totalTests:0,
		time:0,
		failures:undefined,
		results:undefined
	};
}

Reporter.EMPTY = -1;
Reporter.SUITE = 0;
Reporter.CASE = 1;
Reporter.TEST = 2;
Reporter.ResultsNode = ResultsNode;
Reporter.EmptyResult = {};

Reporter.prototype = {
	constructor: Reporter,
	
	reportStarted: function() {
		
		this.StartTime = this.getTime();
		this.CurrentParent = this.TopResults = new ResultsNode({},Reporter.EMPTY,undefined);
	},
	
	reportDone: function() {
		
		if (this.CurrentParent !== this.TopResults) {
			throw new Error('[Reporter.ReportDone] not closed '+this.CurrentParent.results.start.name);
		}
		this.Report.time = this.calcTime(this.StartTime);
		this.OnReportDone(this.getReport());
	},
	
	suiteStarted: function(result) {
		
		var type = this.CurrentParent.results.type;
		if (type !== Reporter.SUITE && type !== Reporter.EMPTY) {
			throw new Error('[Reporter.suiteStarted] '+this.CurrentParent.results.start.name+' is not a suite or is first suite');
		}
		this.onStarted(result,Reporter.SUITE);
		++this.Report.totalSuites;
	},
	
	suiteDone: function(result) {
		
		if (this.CurrentParent === this.TopResults) {
			return;
		}
		this.onDone(result);
	},
	
	caseStarted: function(result) {
		
		if (this.CurrentParent.results.type !== Reporter.SUITE) {
			throw new Error('[Reporter.caseStarted] '+this.CurrentParent.results.start.name+' is not a suite');
		}
		this.onStarted(result,Reporter.CASE);
		++this.Report.totalCases;
	},
	
	caseDone: function(result) {
		
		if (this.CurrentParent.results.type !== Reporter.CASE) {
			throw new Error('[Reporter.caseDone] '+this.CurrentParent.results.start.name+' is not a case');
		}
		this.onDone(result);
	},
	
	testStarted: function(result) {
		
		if (this.CurrentParent.results.type !== Reporter.CASE) {
			throw new Error('[Reporter.testStarted] '+this.CurrentParent.results.start.name+' is not a test case');
		}
		this.onStarted(result,Reporter.TEST);
		++this.Report.totalTests;
	},
	
	testDone: function(result) {
		
		if (this.CurrentParent.results.type !== Reporter.TEST) {
			throw new Error('[Reporter.testDone] '+this.CurrentParent.results.start.name+' is not a test');
		}
		
		var current = this.CurrentParent;
		this.onDone(result);
		
		if (result.failed) {
			current.results.end.fullname = this.FullName.slice(0,this.NameLevel+1).join(' < ');
			this.Failures[this.Failures.length] = current.getResults();
			this.setFailed(current);
		}
		
	},
	
	reportEmptyTest: function() {
		var results = this.CurrentParent.results;
		results.start.name = '_EMPTY_ '+results.start.name;
		this.testDone({failed:true});
		results.end.fullname = '_EMPTY_ '+results.end.fullname;
	},
	
	onStarted: function(result, type) {
		this.CurrentParent = this.CurrentParent.addChild(result,type);
		this.CurrentParent.startTime = this.getTime();
		this.FullName[this.NameLevel++] = result.name;
	},
	
	onDone: function(result) {
		this.CurrentParent.results.end = result||this.CurrentParent.results.end;
		this.CurrentParent.results.time = this.calcTime(this.CurrentParent.startTime);
		this.CurrentParent = this.CurrentParent.parent;
		--this.NameLevel;
	},
	
	setFailed: function(current) {
		current.results.end.failed = true;
		if (current.parent.results.end.failed) {
			return;
		}
		
		current = current.parent;
		do {
			current.results.end.failed = true;
			current = current.parent;
		} while(current !== this.TopResults);
	},
	
	getTime: function() {
		return Date.now();
	},
	
	calcTime: function(start) {
		return (this.getTime() - start) / 1000;
	},
	
	getReport: function() {
		this.Report.results = this.CurrentParent.getResults();
		this.Report.failures = this.Failures;
		
		return this.Report;
	}
};

return Reporter;
});

